<template>
  <div class="knowledge-graph-container">
    <!-- 左上角：图谱关系icon和搜索区域 -->
    <div class="control-panel-top-left">
      <div class="header-section">
        <div class="header-title">
          <i class="fas fa-project-diagram"></i>
          <span>图谱关系</span>
        </div>
      </div>
      <div class="search-section">
        <a-input
          v-model:value="searchKeyword"
          placeholder="搜索节点（支持模糊匹配）..."
          style="width: 250px"
          @pressEnter="handleSearch"
        />
        <a-button type="primary" @click="handleSearch">
          <template #icon><i class="fas fa-search"></i></template>
          搜索
        </a-button>
        <a-button @click="handleClear"> 清除 </a-button>
      </div>
    </div>

    <!-- 右上角：导出和重置按钮 -->
    <div class="control-panel-top-right">
      <a-button type="primary" @click="exportSvg">
        <template #icon><i class="fas fa-download"></i></template>
        导出SVG
      </a-button>
      <a-button type="primary" @click="exportPng">
        <template #icon><i class="fas fa-download"></i></template>
        导出PNG
      </a-button>
      <a-button type="primary" @click="resetView">
        <template #icon><i class="fas fa-home"></i></template>
        重置视图
      </a-button>
    </div>

    <!-- 右下角：放大缩小按钮（水平排列） -->
    <div class="zoom-controls">
      <a-button shape="circle" @click="zoomIn" title="放大">
        <template #icon><i class="fas fa-plus"></i></template>
      </a-button>
      <a-button shape="circle" @click="zoomOut" title="缩小">
        <template #icon><i class="fas fa-minus"></i></template>
      </a-button>
    </div>

    <div ref="chartContainer" class="chart-container"></div>

    <!-- 左侧触发器按钮 -->
    <div class="trigger-btn" @click="drawerOpen = true">
      <span>&gt;</span>
    </div>
    <!-- 遮罩层 -->
    <div v-if="drawerOpen" class="drawer-mask" @click="drawerOpen = false"></div>
    <!-- 自定义毛玻璃抽屉 -->
    <div class="custom-drawer" :class="{ open: drawerOpen }">
      <div class="drawer-content" @click.stop>
        <div class="drawer-close" @click="drawerOpen = false">&times;</div>
        <div class="drawer-form">
          <div class="drawer-tabs">
            <div
              :class="['drawer-tab', tabKey === 'select' && 'active']"
              @click="tabKey = 'select'"
            >
              选择已有知识大纲
            </div>
            <div :class="['drawer-tab', tabKey === 'input' && 'active']" @click="tabKey = 'input'">
              自主填写知识大纲
            </div>
            <div :class="['drawer-tab', tabKey === 'list' && 'active']" @click="tabKey = 'list'">
              知识图谱列表
            </div>
          </div>
          <div v-if="tabKey === 'select'" class="drawer-pane">
            <select v-model="formState.generateTeachId" class="drawer-select">
              <option v-for="item in teachDataList" :key="item.id" :value="item.id">
                {{ item.title }}
              </option>
            </select>
            <textarea
              v-if="formState.generateTeachId"
              v-model="selectedOutline"
              rows="16"
              class="drawer-textarea"
              placeholder="请选择知识大纲..."
            ></textarea>
          </div>
          <div v-else-if="tabKey === 'input'" class="drawer-pane">
            <textarea
              v-model="formState.outline"
              rows="16"
              :maxlength="20000"
              class="drawer-textarea"
              placeholder="请输入知识大纲..."
            ></textarea>
          </div>
          <div v-else-if="tabKey === 'list'" class="drawer-pane drawer-graph-list">
            <ul>
              <li v-for="record in dataList" :key="record.id" @dblclick="handleSwitchGraph(record)">
                <div class="graph-title">{{ record.title }}</div>
                <div class="graph-outline">{{ record.description }}</div>
              </li>
            </ul>
          </div>
          <div v-if="tabKey !== 'list'" style="text-align: right; margin-top: 24px">
            <button class="drawer-btn" @click="handleSubmit">生成知识图谱</button>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted, nextTick, reactive, computed } from 'vue'
import * as echarts from 'echarts'
import { Input as AInput, Button as AButton } from 'ant-design-vue'
import { listGenerateTeachVoByPage } from '@/api/generateTeachController'
import { message } from 'ant-design-vue'
import { addKnowledgeGraph, listKnowledgeGraphByPage } from '@/api/knowledgeGraphController'

// 使用 API 中已有的类型定义，不再重复定义 RawNode, RawLink, RawApiData
// 改用 API.Node, API.Link, API.GraphOption
interface ProcessedNode extends API.Node {
  id: string
  value?: string
  parent?: string
  x?: number
  y?: number
  symbolSize?: number
  itemStyle?: { color: string }
  label?: any
}

interface ProcessedLink {
  source: string
  target: string
  isChapterLink?: boolean
  label?: any
  lineStyle?: any
}

interface ProcessedData {
  nodes: ProcessedNode[]
  links: ProcessedLink[]
}

// 响应式数据
const chartContainer = ref<HTMLDivElement>()
const searchKeyword = ref('')
const currentZoom = ref(1)

// ECharts实例
let myChart: echarts.ECharts | null = null
let chartNodes: ProcessedNode[] = []
let chartLinks: ProcessedLink[] = []

// 数据适配器：将API数据转换为完整的图谱数据
function adaptApiData(rawApiData: API.GraphOption): ProcessedData {
  const { nodes: rawNodes = [], links: rawLinks = [] } = rawApiData

  // 生成节点ID的辅助函数
  function generateNodeId(name: string, category: number): string {
    if (category === 0) return 'center'
    if (category === 1) {
      // 章节节点：c1, c2, c3...
      const chapterIndex =
        rawNodes.filter((n) => n.category === 1).findIndex((n) => n.name === name) + 1
      return `c${chapterIndex}`
    }
    if (category === 2) {
      // 主题节点：根据父节点和索引生成 t1_1, t2_1...
      return `topic_${Math.random().toString(36).substr(2, 9)}` // 临时ID，后续会更新
    }
    return `node_${Math.random().toString(36).substr(2, 9)}`
  }

  // 第一步：为所有节点生成ID
  const processedNodes: ProcessedNode[] = rawNodes.map((node) => ({
    ...node,
    id: generateNodeId(node.name || '', node.category || 0),
    value: node.category === 0 ? `${node.name} - 所有课程的总体结构` : undefined,
  }))

  // 第二步：根据连接关系推断parent属性和更新主题节点ID
  const nodeIdMap = new Map<string, string>() // name -> id 映射
  processedNodes.forEach((node) => {
    nodeIdMap.set(node?.name, node.id)
  })

  // 为主题节点添加parent属性并重新生成ID
  rawLinks.forEach((link) => {
    const sourceNode = processedNodes.find(
      (n) => nodeIdMap.get(link.source) === n.id || n.name === link.source,
    )
    const targetNode = processedNodes.find(
      (n) => nodeIdMap.get(link.target) === n.id || n.name === link.target,
    )

    if (sourceNode && targetNode) {
      // 如果源节点是章节(category=1)，目标节点是主题(category=2)
      if (sourceNode.category === 1 && targetNode.category === 2) {
        targetNode.parent = sourceNode.id

        // 重新生成主题节点ID
        const chapterIndex = sourceNode.id.replace('c', '')
        const topicIndex = processedNodes.filter(
          (n) => n.category === 2 && n.parent === sourceNode.id,
        ).length
        targetNode.id = `t${chapterIndex}_${topicIndex}`
      }
    }
  })

  // 第三步：处理连接关系
  const processedLinks: ProcessedLink[] = rawLinks
    .map((link) => {
      const sourceNode = processedNodes.find((n) => n.name === link.source)
      const targetNode = processedNodes.find((n) => n.name === link.target)

      if (!sourceNode || !targetNode) {
        console.warn(`无法找到连接的节点: ${link.source} -> ${link.target}`)
        return null
      }

      const processedLink: ProcessedLink = {
        source: sourceNode.id,
        target: targetNode.id,
      }

      // 判断是否为章节连接线（中心到章节）
      if (sourceNode.category === 0 && targetNode.category === 1) {
        processedLink.isChapterLink = true
      }

      return processedLink
    })
    .filter((link): link is ProcessedLink => link !== null)

  return {
    nodes: processedNodes,
    links: processedLinks,
  }
}

// 模拟API数据（简化格式）
function mockApiData(): API.GraphOption {
  return {
    nodes: [
      { name: '课程图谱', category: 0 },
      { name: '第1章 机器学习基础', category: 1 },
      { name: '第2章 JavaScript与深度学习', category: 1 },
      { name: '第3章 TensorFlow.js核心概念', category: 1 },
      { name: '第4章 模型构建与训练', category: 1 },
      { name: '第5章 内存与性能优化', category: 1 },
      { name: '机器学习的基本原理', category: 2 },
      { name: '监督学习的定义与机制', category: 2 },
      { name: '神经网络的结构原理', category: 2 },
      { name: 'JavaScript在机器学习中的应用原理', category: 2 },
      { name: '浏览器端深度学习的优势与实现机制', category: 2 },
      { name: 'Node.js与TensorFlow.js的集成原理', category: 2 },
      { name: '张量的定义与数学表示', category: 2 },
      { name: '变量的定义与可变性机制', category: 2 },
      { name: '操作（Ops）的实现机制与分类', category: 2 },
      { name: '模型的定义与训练机制', category: 2 },
      { name: 'Layers API的抽象层次与实现原理', category: 2 },
      { name: 'Core API的底层机制与适用场景', category: 2 },
      { name: 'Sequential模型的构建原理', category: 2 },
      { name: 'Functional模型的构建原理', category: 2 },
      { name: 'GPU加速的实现机制', category: 2 },
      { name: 'WebGL与TensorFlow.js的集成原理', category: 2 },
      { name: '张量内存管理的实现机制', category: 2 },
      { name: 'tf.dispose()的内存释放原理', category: 2 },
      { name: 'tf.tidy()的内存自动管理机制', category: 2 },
    ],
    links: [
      { source: '课程图谱', target: '第1章 机器学习基础' },
      { source: '课程图谱', target: '第2章 JavaScript与深度学习' },
      { source: '课程图谱', target: '第3章 TensorFlow.js核心概念' },
      { source: '课程图谱', target: '第4章 模型构建与训练' },
      { source: '课程图谱', target: '第5章 内存与性能优化' },
      { source: '第1章 机器学习基础', target: '机器学习的基本原理' },
      { source: '第1章 机器学习基础', target: '监督学习的定义与机制' },
      { source: '第1章 机器学习基础', target: '神经网络的结构原理' },
      { source: '第2章 JavaScript与深度学习', target: 'JavaScript在机器学习中的应用原理' },
      { source: '第2章 JavaScript与深度学习', target: '浏览器端深度学习的优势与实现机制' },
      { source: '第2章 JavaScript与深度学习', target: 'Node.js与TensorFlow.js的集成原理' },
      { source: '第3章 TensorFlow.js核心概念', target: '张量的定义与数学表示' },
      { source: '第3章 TensorFlow.js核心概念', target: '变量的定义与可变性机制' },
      { source: '第3章 TensorFlow.js核心概念', target: '操作（Ops）的实现机制与分类' },
      { source: '第3章 TensorFlow.js核心概念', target: '模型的定义与训练机制' },
      { source: '第4章 模型构建与训练', target: 'Layers API的抽象层次与实现原理' },
      { source: '第4章 模型构建与训练', target: 'Core API的底层机制与适用场景' },
      { source: '第4章 模型构建与训练', target: 'Sequential模型的构建原理' },
      { source: '第4章 模型构建与训练', target: 'Functional模型的构建原理' },
      { source: '第5章 内存与性能优化', target: 'GPU加速的实现机制' },
      { source: '第5章 内存与性能优化', target: 'WebGL与TensorFlow.js的集成原理' },
      { source: '第5章 内存与性能优化', target: '张量内存管理的实现机制' },
      { source: '第5章 内存与性能优化', target: 'tf.dispose()的内存释放原理' },
      { source: '第5章 内存与性能优化', target: 'tf.tidy()的内存自动管理机制' },
    ],
  }
}

// 手动计算节点坐标
function calculateNodePositions(apiData: ProcessedData): {
  nodes: ProcessedNode[]
  links: ProcessedLink[]
} {
  const centerX = 400
  const centerY = 300
  const chapterRadius = 200 // 章节节点距离中心的半径
  const topicRadius = 120 // 主题节点距离章节节点的半径

  const processedNodes: ProcessedNode[] = []
  const processedLinks: ProcessedLink[] = []

  // 筛选不同类型的节点
  const rootNode = apiData.nodes.find((n) => n.category === 0)
  const chapterNodes = apiData.nodes.filter((n) => n.category === 1)
  const topicNodes = apiData.nodes.filter((n) => n.category === 2)

  // 处理根节点（固定在中心）
  if (rootNode) {
    processedNodes.push({
      ...rootNode,
      x: centerX,
      y: centerY,
      symbolSize: 80,
      itemStyle: { color: '#C871F4' },
      label: {
        show: true,
        position: 'inside',
        color: '#ffffff',
        fontSize: 14,
        fontWeight: 'bold',
      },
    })
  }

  // 处理章节节点（环形分布）
  chapterNodes.forEach((node, index) => {
    const angle = (index / chapterNodes.length) * 2 * Math.PI
    const x = centerX + chapterRadius * Math.cos(angle)
    const y = centerY + chapterRadius * Math.sin(angle)

    processedNodes.push({
      ...node,
      x: x,
      y: y,
      symbolSize: 40,
      itemStyle: { color: '#524BE5' },
      label: {
        show: false,
      },
    })
  })

  // 处理主题节点（基于父章节位置）
  topicNodes.forEach((node) => {
    const parentChapter = processedNodes.find((n) => n.id === node.parent)
    if (parentChapter) {
      // 获取父章节相对于中心的角度
      const parentAngle = Math.atan2(parentChapter.y! - centerY, parentChapter.x! - centerX)

      // 计算该章节下的主题节点数量和当前索引
      const siblingTopics = topicNodes.filter((t) => t.parent === node.parent)
      const currentIndex = siblingTopics.findIndex((t) => t.id === node.id)

      // 在父章节周围扇形分布主题节点
      const offsetAngle = (currentIndex - (siblingTopics.length - 1) / 2) * 0.5
      const finalAngle = parentAngle + offsetAngle

      const x = parentChapter.x! + topicRadius * Math.cos(finalAngle)
      const y = parentChapter.y! + topicRadius * Math.sin(finalAngle)

      // 计算标签位置（在节点外侧）
      let labelPosition = 'right'
      if (finalAngle > Math.PI / 2 && finalAngle < (3 * Math.PI) / 2) {
        labelPosition = 'left'
      }

      processedNodes.push({
        ...node,
        x: x,
        y: y,
        symbolSize: 18,
        itemStyle: { color: '#4893FD' },
        label: {
          show: true,
          position: labelPosition,
          color: '#e5e7eb',
          fontSize: 9,
          distance: 15,
        },
      })
    }
  })

  // 处理连接线
  apiData.links.forEach((link) => {
    const linkData: ProcessedLink = { ...link }

    // 为章节连接线添加标签
    if (link.isChapterLink) {
      const targetNode = processedNodes.find((n) => n.id === link.target)
      linkData.label = {
        show: true,
        formatter: targetNode ? targetNode.name : '',
        fontSize: 13,
        color: '#fbbf24',
        backgroundColor: 'rgba(17, 24, 39, 0.8)',
        borderRadius: 4,
        padding: [2, 6],
      }
      linkData.lineStyle = {
        color: '#fbbf24',
        width: 2,
        opacity: 0.9,
      }
    } else {
      linkData.lineStyle = {
        color: '#4b5563',
        width: 1.5,
        opacity: 0.7,
      }
    }

    processedLinks.push(linkData)
  })

  return { nodes: processedNodes, links: processedLinks }
}

// mask 实现：通过下调全局透明度并对目标节点/边提升样式
function applyMask(highlightIds: string[]) {
  if (!myChart) return

  const setDim = (dim: boolean) => {
    myChart!.setOption({
      series: [
        {
          lineStyle: { opacity: dim ? 0.1 : 0.9 },
          itemStyle: { opacity: dim ? 0.15 : 1 },
          label: { opacity: dim ? 0.3 : 1 },
        },
      ],
    })
  }

  const resetStyle = () => setDim(false)

  if (!highlightIds || highlightIds.length === 0) {
    resetStyle()
    return
  }

  setDim(true)

  // 高亮指定节点
  const highlightDataIndices: number[] = []
  highlightIds.forEach((id) => {
    const idx = chartNodes.findIndex((n) => n.id === id)
    if (idx >= 0) highlightDataIndices.push(idx)
  })

  myChart.dispatchAction({ type: 'downplay', seriesIndex: 0 })
  // 注意：在 layout: 'none' 下，dataIndex 由 series.data 的顺序决定
  highlightDataIndices.forEach((di) => {
    myChart!.dispatchAction({
      type: 'highlight',
      seriesIndex: 0,
      dataIndex: di,
    })
  })
}

// 绑定悬浮事件
function bindHoverMask() {
  if (!myChart) return

  myChart.on('mouseover', function (params: any) {
    if (params.dataType === 'node') {
      const nodeId = params.data.id
      const neighborIds = new Set([nodeId])
      chartLinks.forEach((l) => {
        if (l.source === nodeId) neighborIds.add(l.target)
        if (l.target === nodeId) neighborIds.add(l.source)
      })
      applyMask([...neighborIds])
    }
  })

  myChart.on('mouseout', function () {
    applyMask([])
  })
}

// 搜索功能
function handleSearch() {
  const kw = searchKeyword.value.trim().toLowerCase()

  if (!kw) {
    applyMask([])
    myChart && myChart.dispatchAction({ type: 'downplay', seriesIndex: 0 })
    return
  }

  const matchedIds = chartNodes
    .filter((n) => {
      return (n.name || '').toLowerCase().includes(kw)
    })
    .map((n) => n.id)

  applyMask(matchedIds)
  const first = matchedIds[0]
  if (first && myChart) {
    const idx = chartNodes.findIndex((n) => n.id === first)
    if (idx >= 0) {
      myChart.dispatchAction({
        type: 'focusNodeAdjacency',
        seriesIndex: 0,
        dataIndex: idx,
      })
    }
  }
}

// 清除搜索
function handleClear() {
  searchKeyword.value = ''
  applyMask([])
  myChart && myChart.dispatchAction({ type: 'downplay', seriesIndex: 0 })
}

// 导出SVG
function exportSvg() {
  if (myChart) {
    const svgUrl = myChart.getDataURL({
      type: 'svg',
      pixelRatio: 1,
      backgroundColor: '#0f172a',
    })
    const link = document.createElement('a')
    link.href = svgUrl
    link.download = 'knowledge-graph.svg'
    link.click()
  }
}

// 导出PNG
function exportPng() {
  if (myChart) {
    const pngUrl = myChart.getDataURL({
      type: 'png',
      pixelRatio: 2,
      backgroundColor: '#0f172a',
    })
    const link = document.createElement('a')
    link.href = pngUrl
    link.download = 'knowledge-graph.png'
    link.click()
  }
}

// 重置视图
function resetView() {
  if (myChart) {
    currentZoom.value = 1
    myChart.dispatchAction({
      type: 'restore',
    })
  }
}

// 放大
function zoomIn() {
  if (myChart) {
    currentZoom.value = Math.min(currentZoom.value * 1.2, 5)
    const option = myChart.getOption()
    option.series[0].zoom = currentZoom.value
    myChart.setOption(option, true)
  }
}

// 缩小
function zoomOut() {
  if (myChart) {
    currentZoom.value = Math.max(currentZoom.value * 0.8, 1)
    const option = myChart.getOption()
    option.series[0].zoom = currentZoom.value
    myChart.setOption(option, true)
  }
}

// 初始化图表
async function initChart() {
  try {
    if (!chartContainer.value) return

    // 获取数据并初始化图表
    const rawData = mockApiData()
    const data = adaptApiData(rawData)
    const positionedData = calculateNodePositions(data)

    // 计算节点位置
    const { nodes, links } = positionedData
    chartNodes = nodes
    chartLinks = links

    // 节点分类
    const categories = [
      { name: '中心', itemStyle: { color: '#C871F4' } },
      { name: '章节', itemStyle: { color: '#524BE5' } },
      { name: '主题', itemStyle: { color: '#4893FD' } },
    ]

    // 初始化ECharts
    myChart = echarts.init(chartContainer.value, null, { renderer: 'canvas' })

    const option = {
      backgroundColor: '#0f172a',
      tooltip: {
        trigger: 'item',
        formatter: function (params: any) {
          if (params.dataType === 'node') {
            return params.data.value || params.data.name
          }
          return `${params.data.source} → ${params.data.target}`
        },
        backgroundColor: 'rgba(17, 24, 39, 0.95)',
        borderColor: '#374151',
        textStyle: {
          color: '#e5e7eb',
        },
      },
      legend: [
        {
          data: categories.map((c) => c.name),
          textStyle: { color: '#e5e7eb' },
          left: 20,
          bottom: 80,
          orient: 'vertical',
          itemGap: 15,
          backgroundColor: 'rgba(17, 24, 39, 0.8)',
          borderRadius: 8,
          padding: [10, 15],
        },
      ],
      series: [
        {
          name: '知识图谱',
          type: 'graph',
          layout: 'none', // 禁用自动布局，使用手动坐标
          roam: true,
          draggable: true,
          zoom: 1,
          scaleLimit: {
            min: 1, // 最小缩放比例为1（不能缩小）
            max: 5, // 最大缩放比例为5倍
          },
          edgeSymbol: ['none', 'none'],
          edgeSymbolSize: 8,
          edgeLabel: {
            show: true,
            formatter: function (params: any) {
              return params.data.label ? params.data.label.formatter : ''
            },
          },
          lineStyle: {
            color: '#4b5563',
            width: 1.5,
            opacity: 0.9,
          },
          label: {
            show: true,
            color: '#e5e7eb',
            fontSize: 12,
          },
          emphasis: {
            focus: 'adjacency',
            itemStyle: {
              shadowBlur: 20,
              shadowColor: 'rgba(251, 191, 36, 0.6)',
            },
          },
          categories: categories,
          data: nodes,
          links: links,
        },
      ],
    }

    myChart.setOption(option)

    // 如果当前已有需要展示的图谱数据（例如列表已先加载完成），在图表初始化后应用
    if (currentData.value) {
      applyGraphOption(currentData.value)
    }

    // 绑定悬浮事件
    bindHoverMask()

    // 自适应
    const handleResize = () => myChart?.resize()
    window.addEventListener('resize', handleResize)

    // 返回清理函数，在组件卸载时调用
    return () => {
      window.removeEventListener('resize', handleResize)
      myChart?.dispose()
    }
  } catch (error) {
    console.log('初始化图表失败:' + error)
    message.error('初始化图表失败:')
  }
}

// 组件挂载后初始化
onMounted(async () => {
  await nextTick()
  const cleanup = await initChart()

  // 组件卸载时清理
  onUnmounted(() => {
    cleanup?.()
  })
})

// 侧边抽屉相关状态与数据
const drawerOpen = ref(false)
const tabKey = ref<'select' | 'input' | 'list'>('select')
// 与页面版保持一致的数据结构
const dataList = ref<API.KnowledgeGraphVO[]>([])
const teachDataList = ref<API.GenerateTeachVO[]>([])
// 加载知识图谱列表的数据
const searchParams = reactive<API.KnowledgeGraphQueryRequest>({
  current: 1,
  pageSize: 10, //固定只能展示3个数据
})
// 加载教学大纲的数据
const queryParams = reactive<API.GenerateTeachQueryDTO>({
  current: 1,
  pageSize: 5, //固定只能展示3个数据
  sortField: 'publishTime',
})

const selectedOutline = computed(() => {
  const idx = teachDataList.value.findIndex((td: any) => td.id === formState.generateTeachId)
  if (idx >= 0) {
    return teachDataList.value[idx].content
  }
  return ''
})
// 加载教学大纲的参数
const formState = reactive<API.KnowledgeGraphAddRequest>({
  generateTeachId: '',
  outline: '',
})
// 加载教学大纲的数据
const fetchTeachData = async () => {
  try {
    const res = await listGenerateTeachVoByPage({ ...searchParams })
    if (res.data?.data && res.data.code === 0) {
      teachDataList.value = res.data.data.records ?? []
    }
  } catch (e) {
    // 忽略错误展示，外部页面里有全局message
    console.error('获取已有大纲列表失败', e)
  }
}

const fetchData = async () => {
  try {
    const res = await listKnowledgeGraphByPage({ ...queryParams })
    if (res.data?.data && res.data.code === 0) {
      dataList.value = res.data.data.records ?? []
      // 初始页面默认加载第一个知识图谱
      if (dataList.value.length > 0) {
        currentData.value = dataList.value[0].graphOption
        if (myChart) {
          applyGraphOption(currentData.value)
        }
      }
    }
  } catch (e) {
    message.error('获取知识图谱列表失败'+ e.message)
  }
}
const currentData = ref<API.GraphOption>()

function applyGraphOption(graphOption?: API.GraphOption) {
  if (!graphOption) return
  const processed = calculateNodePositions(adaptApiData(graphOption))
  chartNodes = processed.nodes
  chartLinks = processed.links
  if (myChart) {
    myChart.setOption(
      {
        series: [
          {
            data: chartNodes,
            links: chartLinks,
          },
        ],
      },
      false,
    )
  }
}

function handleSwitchGraph(record: API.KnowledgeGraphVO) {
  currentData.value = record.graphOption
  // 重新渲染知识图谱（保持展示形式不变，仅替换数据）
  applyGraphOption(currentData.value)
  drawerOpen.value = false
}

const handleSubmit = async () => {
  try {
    const res = await addKnowledgeGraph({
      ...formState,
    })
    if (res.data?.data && res.data.code === 0) {
      await fetchData()
      // 成功后可以提示并关闭抽屉
      drawerOpen.value = false
    }
  } catch (e) {
    message.error('知识图谱生成失败')
  }
}

// 在组件挂载时，同时初始化图表并请求抽屉所需列表
onMounted(async () => {
  // existing: initChart() 已在文件底部的 onMounted 内调用，这里不重复
  fetchTeachData()
  fetchData()
})
</script>

<style scoped>
.knowledge-graph-container {
  position: relative;
  width: 100%;
  height: 93.5vh;
  margin: 0;
  padding: 0;
  overflow: hidden;
  background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #334155 100%);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Microsoft YaHei', sans-serif;
  box-shadow: inset 0 0 100px rgba(59, 130, 246, 0.1);
}

.chart-container {
  width: 100%;
  height: 100vh;
  border-radius: 8px;
  overflow: hidden;
}

.control-panel-top-left {
  position: absolute;
  top: 16px;
  left: 16px;
  z-index: 50;
  background: rgba(15, 23, 42, 0.8);
  border-radius: 16px;
  padding: 20px;
  border: 1px solid rgba(59, 130, 246, 0.2);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
  backdrop-filter: blur(10px);
  display: flex;
  align-items: center;
  gap: 12px;
}

.header-section {
  display: flex;
  align-items: center;
  margin-bottom: 10px;
}

.header-title {
  display: flex;
  align-items: center;
  gap: 12px;
  color: #f8fafc;
  font-size: 20px;
  font-weight: 700;
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}

.header-title i {
  color: #60a5fa;
  font-size: 24px;
  filter: drop-shadow(0 0 8px rgba(96, 165, 250, 0.6));
  animation: pulse 2s infinite;
}

@keyframes pulse {
  0%,
  100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.05);
  }
}

.search-section {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.control-panel-top-right {
  position: absolute;
  top: 16px;
  right: 16px;
  z-index: 50;
  background: rgba(15, 23, 42, 0.8);
  border-radius: 16px;
  padding: 16px;
  border: 1px solid rgba(59, 130, 246, 0.2);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
  backdrop-filter: blur(10px);
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.zoom-controls {
  position: absolute;
  bottom: 15px;
  right: 16px;
  z-index: 50;
  background: rgba(15, 23, 42, 0.8);
  border-radius: 50px;
  padding: 12px 16px;
  border: 1px solid rgba(59, 130, 246, 0.2);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
  backdrop-filter: blur(10px);
  display: flex;
  flex-direction: row;
  gap: 12px;
  align-items: center;
}

/* Ant Design 组件样式定制 */
:deep(.ant-input) {
  background: rgba(30, 41, 59, 0.9);
  border: 1px solid rgba(59, 130, 246, 0.3);
  color: #f1f5f9;
  border-radius: 12px;
  padding: 8px 16px;
  font-size: 14px;
  transition: all 0.3s ease;
  backdrop-filter: blur(5px);
}

:deep(.ant-input:focus) {
  border-color: #60a5fa;
  box-shadow:
    0 0 0 3px rgba(96, 165, 250, 0.2),
    0 0 20px rgba(96, 165, 250, 0.1);
  background: rgba(30, 41, 59, 0.95);
}

:deep(.ant-input::placeholder) {
  color: #94a3b8;
  font-style: italic;
}

:deep(.ant-btn) {
  border-radius: 12px;
  font-weight: 600;
  font-size: 14px;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  backdrop-filter: blur(10px);
  border: 1px solid transparent;
  position: relative;
  overflow: hidden;
}

:deep(.ant-btn::before) {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
  transition: left 0.5s;
}

:deep(.ant-btn:hover::before) {
  left: 100%;
}

:deep(.ant-btn-primary) {
  background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
  border-color: #3b82f6;
  box-shadow: 0 4px 15px rgba(59, 130, 246, 0.3);
}

:deep(.ant-btn-primary:hover) {
  background: linear-gradient(135deg, #2563eb 0%, #1e40af 100%);
  border-color: #2563eb;
  transform: translateY(-2px);
  box-shadow: 0 8px 25px rgba(59, 130, 246, 0.4);
}

:deep(.ant-btn:not(.ant-btn-primary)) {
  background: rgba(51, 65, 85, 0.8);
  border-color: rgba(71, 85, 105, 0.6);
  color: #f1f5f9;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}

:deep(.ant-btn:not(.ant-btn-primary):hover) {
  background: rgba(71, 85, 105, 0.9);
  border-color: rgba(96, 165, 250, 0.5);
  color: #ffffff;
  transform: translateY(-2px);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
}

:deep(.ant-btn-circle) {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: rgba(51, 65, 85, 0.9);
  border: 2px solid rgba(96, 165, 250, 0.3);
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}

:deep(.ant-btn-circle:hover) {
  background: rgba(71, 85, 105, 0.95);
  border-color: rgba(96, 165, 250, 0.6);
  box-shadow: 0 6px 20px rgba(96, 165, 250, 0.2);
}

:deep(.ant-btn i) {
  font-size: 16px;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3));
}

:deep(.ant-btn-circle i) {
  font-size: 18px;
  color: #60a5fa;
}

:deep(.ant-btn-primary i) {
  color: #ffffff;
}

/* 添加按钮点击效果 */
:deep(.ant-btn:active) {
  transform: translateY(0);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}

/* 搜索按钮特殊样式 */
:deep(.search-section .ant-btn-primary) {
  background: linear-gradient(135deg, #10b981 0%, #059669 100%);
  border-color: #10b981;
}

:deep(.search-section .ant-btn-primary:hover) {
  background: linear-gradient(135deg, #059669 0%, #047857 100%);
  box-shadow: 0 8px 25px rgba(16, 185, 129, 0.4);
}

/* 导出按钮特殊样式 */
:deep(.control-panel-top-right .ant-btn-primary) {
  background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
  border-color: #8b5cf6;
}

:deep(.control-panel-top-right .ant-btn-primary:hover) {
  background: linear-gradient(135deg, #7c3aed 0%, #6d28d9 100%);
  box-shadow: 0 8px 25px rgba(139, 92, 246, 0.4);
}

/* 重置按钮特殊样式 */
:deep(.control-panel-top-right .ant-btn:last-child) {
  background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
  border-color: #f59e0b;
  color: #ffffff;
}

:deep(.control-panel-top-right .ant-btn:last-child:hover) {
  background: linear-gradient(135deg, #d97706 0%, #b45309 100%);
  box-shadow: 0 8px 25px rgba(245, 158, 11, 0.4);
}
/* Drawer styles (migrated from KnowledgeGraphPage.vue) */
.trigger-btn {
  position: absolute;
  left: 0;
  top: 50%;
  z-index: 1002;
  width: 40px;
  height: 80px;
  background: rgba(0, 0, 0, 0.5);
  color: #fff;
  border-radius: 0 20px 20px 0;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: left 0.3s cubic-bezier(0.23, 1, 0.32, 1);
  font-size: 32px;
  user-select: none;
  transform: translateY(-50%);
  box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
}
.trigger-btn:hover {
  left: 8px;
  background: #1677ff;
}
.custom-drawer {
  position: absolute;
  top: 150px; /* 避开顶部控制面板 */
  left: 0;
  width: 40vw;
  max-width: 520px;
  min-height: 520px;
  max-height: 600px;
  z-index: 1002;
  background: rgba(20, 24, 40, 0.5);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  box-shadow: 2px 0 24px rgba(0, 0, 0, 0.25);
  border-radius: 0 18px 18px 0;
  transform: translateX(-100%);
  transition: transform 0.4s cubic-bezier(0.23, 1, 0.32, 1);
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  overflow: visible;
}
.custom-drawer.open {
  transform: translateX(0);
}
.drawer-mask {
  position: absolute;
  top: 20px;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.68);
  z-index: 1001;
}
.drawer-content {
  width: 100%;
  min-height: 320px;
  max-height: 600px;
  overflow-y: auto;
  padding: 32px 24px 24px 24px;
  position: relative;
  box-sizing: border-box;
  background: transparent;
  border-radius: 0 18px 18px 0;
}
.drawer-close {
  position: absolute;
  right: 18px;
  top: 18px;
  font-size: 28px;
  color: #fff;
  cursor: pointer;
  z-index: 2;
  opacity: 0.7;
  transition: opacity 0.2s;
}
.drawer-close:hover {
  opacity: 1;
}
.drawer-form {
  margin-top: 8px;
}
.drawer-tabs {
  display: flex;
  border-bottom: 1.5px solid #e0e0e0;
  margin-bottom: 18px;
}
.drawer-tab {
  padding: 8px 18px;
  cursor: pointer;
  color: #888;
  font-weight: 500;
  border-bottom: 2px solid transparent;
  transition:
    color 0.2s,
    border-color 0.2s;
}
.drawer-tab.active {
  color: #1677ff;
  border-bottom: 2px solid #1677ff;
  background: rgba(255, 255, 255, 0.05);
}
.drawer-pane {
  margin-top: 12px;
}
.drawer-select,
.drawer-textarea {
  width: 100%;
  font-size: 16px;
  border-radius: 6px;
  border: 1px solid #d9d9d9;
  padding: 8px 12px;
  margin-bottom: 8px;
  background: rgba(255, 255, 255, 0.15);
  color: #222;
  outline: none;
  box-sizing: border-box;
  resize: vertical;
}
.drawer-btn {
  background: #1677ff;
  color: #fff;
  border: none;
  border-radius: 6px;
  padding: 8px 24px;
  font-size: 16px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s;
}
.drawer-btn:hover {
  background: #0958d9;
}
.drawer-graph-list ul {
  list-style: none;
  padding: 0;
  margin: 0;
}
.drawer-graph-list li {
  padding: 12px 10px;
  border-radius: 8px;
  margin-bottom: 10px;
  background: rgba(255, 255, 255, 0.06);
  color: #f5f5f5;
  cursor: pointer;
}
.drawer-graph-list li:hover {
  background: rgba(255, 255, 255, 0.12);
}
.graph-title {
  font-size: 16px;
  font-weight: 600;
  margin-bottom: 6px;
}
.graph-outline {
  font-size: 13px;
  color: #ddd;
}
</style>
